/*
 * Copyright 2005-2008 Pulse Technologies. Tutti i diritti riservati.
 */
package org.obsidianrad.server.services.user.db;


import java.io.IOException;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;

import javax.sql.DataSource;

import org.obsidianrad.server.services.user.ObsidianUserDetails;
import org.obsidianrad.utils.ObjectSerializer;
import org.springframework.context.ApplicationContextException;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.MappingSqlQuery;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.jdbc.JdbcDaoImpl;

/**
 * 
 * @author Eligio Colutta
 * @version $Rev: 8 $
 * @lastautorcommit $Author: eliosh $
 * @lastdatecommit $Date: 2009-11-05 02:03:33 +0100(gio, 05 nov 2009) $
 */
public class ObsidianUserDetailsService extends JdbcDaoImpl {

	private static final Logger log = Logger.getLogger(ObsidianUserDetailsService.class.getCanonicalName());

	// ~ Static fields/initializers
	// =====================================================================================

	public static final String DEF_OBSUSERS_BY_USERNAME_QUERY = "SELECT username, password, enabled, isadmin, preferences FROM obs_users WHERE username = ?";

	public static final String DEF_OBSAUTHORITIES_BY_USERNAME_QUERY = "select au.username, concat(au.authority, \"#\", au.context) as authority from obs_authorities au WHERE username = ?";

	private MappingSqlQuery usersByUsernameMapping;

	// ~ Constructors
	// ===================================================================================================

	public ObsidianUserDetailsService() {
		super();
		this.setUsersByUsernameQuery(DEF_OBSUSERS_BY_USERNAME_QUERY);
		this.setAuthoritiesByUsernameQuery(DEF_OBSAUTHORITIES_BY_USERNAME_QUERY);
	}

	@Override
	protected void initDao() throws ApplicationContextException {
		super.initDao();
		this.usersByUsernameMapping = new ObsUsersByUsernameMapping(getDataSource());
	}

	@SuppressWarnings("unchecked")
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
		List users = loadUsersByUsername(username);

		if (users.size() == 0) {
			throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.notFound", new Object[] { username }, "Username {0} not found"), username);
		}

		UserDetails user = (UserDetails) users.get(0); // contains no
														// GrantedAuthority[]

		Set dbAuthsSet = new HashSet();

		if (this.getEnableAuthorities()) {
			dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));
		}

		if (this.getEnableGroups()) {
			dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
		}

		List dbAuths = new ArrayList(dbAuthsSet);

		addCustomAuthorities(user.getUsername(), dbAuths);

		if (dbAuths.size() == 0) {
			throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.noAuthority", new Object[] { username }, "User {0} has no GrantedAuthority"), username);
		}

		GrantedAuthority[] arrayAuths = (GrantedAuthority[]) dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);

		return createUserDetails(username, user, arrayAuths);

	}

	/**
	 * Can be overridden to customize the creation of the final
	 * UserDetailsObject returnd from <tt>loadUserByUsername</tt>.
	 * 
	 * @param username
	 *            the name originally passed to loadUserByUsername
	 * @param userFromUserQuery
	 *            the object returned from the execution of the
	 * @param combinedAuthorities
	 *            the combined array of authorities from all the authority
	 *            loading queries.
	 * @return the final UserDetails which should be used in the system.
	 */
	@Override
	protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery, GrantedAuthority[] combinedAuthorities) {
		String returnUsername = userFromUserQuery.getUsername();

		ObsidianUserDetails u = new ObsidianUserDetails(returnUsername, userFromUserQuery.getPassword(), userFromUserQuery.isEnabled(), true, true, true, combinedAuthorities);
		boolean canSuperAdmin = ((ObsidianUserDetails) userFromUserQuery).isCanSuperAdmin();
		u.setCanSuperAdmin(canSuperAdmin);
		u.setPreferences(new HashMap<String, Serializable>());
		return u;
	}

	@SuppressWarnings("unchecked")
	@Override
	protected List loadUsersByUsername(String username) {
		return usersByUsernameMapping.execute(username);
	}

	/**
	 * Query object to look up a user.
	 */
	private class ObsUsersByUsernameMapping extends MappingSqlQuery {
		protected ObsUsersByUsernameMapping(DataSource ds) {
			super(ds, DEF_OBSUSERS_BY_USERNAME_QUERY);
			declareParameter(new SqlParameter(Types.VARCHAR));
			compile();
		}

		@SuppressWarnings("unchecked")
		protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
			String username = rs.getString(1);
			String password = rs.getString(2);
			boolean enabled = rs.getBoolean(3);
			boolean canSuperAdmin = rs.getBoolean(4);
			byte[] buf = rs.getBytes(5);

			HashMap<String, Serializable> prefs = null; // preferenze utente

			if (buf != null) {
				// Decompress the bytes
				try {
					prefs = (HashMap<String, Serializable>) ObjectSerializer.unserialize(buf, true);
				} catch (IOException e) {
					log.severe("Error in decompressing user preferences : " + e.getLocalizedMessage());
					e.printStackTrace();
				} catch (DataFormatException e) {
					log.severe("Error in decompressing user preferences : " + e.getLocalizedMessage());
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					log.severe("Error in decompressing user preferences : " + e.getLocalizedMessage());
					e.printStackTrace();
				}
			}

			ObsidianUserDetails user = new ObsidianUserDetails(username, password, enabled, true, true, true, new GrantedAuthority[] { new GrantedAuthorityImpl("HOLDER") });
			user.setCanSuperAdmin(canSuperAdmin);
			user.setPreferences(prefs);
			return user;
		}
	}
}
